package com.tongshu.middleware.db.router.util;

import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @description: 属性工具类
 * @author：桐树
 * @date: 2022/1/5
 */
public class PropertyUtil {
    private static int springBootVersion = 1;

    static {
        try {
            Class.forName("org.springframework.boot.bind.RelaxedPropertyResolver");
        } catch (ClassNotFoundException e) {
            springBootVersion = 2;
        }
    }

    /**
     * Spring Boot 1.x 通过使用Java反射兼容Spring Boot 2.x。
     * @param environment 环境上下文
     * @param prefix 属性字段前缀
     * @param targetClass 结果的类别
     * @param <T> 结果类泛型
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T handle(final Environment environment, final String prefix, final Class<T> targetClass) {
        switch (springBootVersion) {
            case 1:
                return (T) v1(environment, prefix);
            default:
                return (T) v2(environment, prefix, targetClass);
        }
    }

    private static Object v1(final Environment environment, final String prefix) {
        try {
            Class<?> resolverClass = Class.forName("org.springframework.boot.bind.RelaxedPropertyResolver");
            Constructor<?> resolverConstructor = resolverClass.getDeclaredConstructor(PropertyResolver.class);
            Method getSubPropertiesMethod = resolverClass.getDeclaredMethod("getSubProperties", String.class);
            Object resolverObject = resolverConstructor.newInstance(environment);
            String prefixParam = prefix.endsWith(".") ? prefix : prefix + ".";
            return getSubPropertiesMethod.invoke(resolverObject, prefixParam);
        } catch (final ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException
        | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            throw new RuntimeException(ex.getMessage(), ex);
        }
    }

    private static Object v2(final Environment environment, final String prefix, final Class<?> targetClass) {
        try {
            // 使用当前类加载器装载Binder类并实例化，Binder可以让你获取一个或多个ConfigurationPropertySource并从它们中绑定一些东西
            Class<?> binderClass = Class.forName("org.springframework.boot.context.properties.bind.Binder");
            /**
             * 方法返回一个Method对象，它反映此Class对象所表示的类或接口的指定已声明方法
             *      name 参数是一个字符串，指定所需的方法的简单名称，
             *      parameterTypes 参数是一个数组的Class对象识别方法的形参类型，在声明的顺序
             * 相当于 binderClass.getEnvironment
             */
            Method getMethod = binderClass.getDeclaredMethod("get", Environment.class);
            /**
             * binderClass.bind(String, Class<T>)
             */
            Method bindMethod = binderClass.getDeclaredMethod("bind", String.class, Class.class);
            /**
             * method.invoke(Object obj,Object args[])的作用就是调用method类代表的方法，其中obj是对象名，args是传入method方法的参数
             * 如果底层方法是静态的，那么可以忽略指定的 obj 参数。该参数可以为 null。
             * 如果底层方法所需的形参数为 0，则所提供的 args 数组长度可以为 0 或 null。
             */
            Object binderObject = getMethod.invoke(null, environment);
            String prefixParam = prefix.endsWith(".") ? prefix.substring(0, (prefix.length() - 1)) : prefix;
            /**
             * binderObject.bind(prefixParam, targetClass)
             */
            Object bindResultObject = bindMethod.invoke(binderObject, prefixParam, targetClass);
            /**
             * bindResultObject.get()
             */
            Method resultGetMethod = bindResultObject.getClass().getDeclaredMethod("get");
            /**
             * bindResultObject
             */
            return resultGetMethod.invoke(bindResultObject);
        } catch (final ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            throw new RuntimeException(ex.getMessage(), ex);
        }
    }
}
